﻿/*
Copyright (c) 2009 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace TeachMe.Application.Controls
{
    public class HotkeyEdit : ErrorEdit
    {
        private Keys _modifiers = Keys.None;
        private Keys _key = Keys.None;
        private bool _modified;
        private bool _winIsDown;

        private static readonly List<Pair<Keys, string>> AllowedKeys = new List<Pair<Keys, string>>();
        private static readonly List<Pair<Keys, string>> AllowedModifiers = new List<Pair<Keys, string>>();

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if ((keyData & Keys.Tab) == Keys.Tab)
            {
                return base.ProcessCmdKey(ref msg, keyData);
            }

            _modifiers = Keys.None;

            foreach (Pair<Keys, string> pair in AllowedModifiers)
            {
                if ((keyData & pair.Key) == pair.Key)
                {
                    _modifiers |= pair.Key;
                }
            }

            if (_winIsDown)
            {
                _modifiers |= Keys.LWin;
            }

            return base.ProcessCmdKey(ref msg, keyData);
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            if ((e.KeyCode & Keys.LWin) == Keys.LWin || (e.KeyCode & Keys.RWin) == Keys.RWin)
            {
                _winIsDown = false;
            }
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            if ((e.KeyCode & Keys.LWin) == Keys.LWin || (e.KeyCode & Keys.RWin) == Keys.RWin)
            {
                _winIsDown = true;
            }

            if (_modifiers != Keys.None && IsKeyAllowed(e.KeyCode))
            {
                _key = e.KeyCode;
                _modified = true;

                UpdateText();
            }

            if (_modifiers == Keys.None && (e.KeyCode == Keys.Back || e.KeyCode == Keys.Delete))
            {
                _key = Keys.None;
                _modifiers = Keys.None;
                _modified = true;

                ClearText();
            }

            e.SuppressKeyPress = true;
            e.Handled = true;
        }

        private void ClearText()
        {
            Text = String.Empty;
        }

        private static bool IsKeyAllowed(Keys key)
        {
            foreach (Pair<Keys, string> pair in AllowedKeys)
            {
                if (pair.Key == key)
                {
                    return true;
                }
            }

            return false;
        }
        
        private static string GetKeyCaption(Keys key)
        {
            foreach (Pair<Keys, string> pair in AllowedKeys)
            {
                if (pair.Key == key)
                {
                    return pair.Value;
                }
            }

            throw new InvalidOperationException();
        }

        static HotkeyEdit()
        {
            AllowedModifiers.Add(new Pair<Keys, string>(Keys.Control, "Control"));
            AllowedModifiers.Add(new Pair<Keys, string>(Keys.Alt, "Alt"));
            AllowedModifiers.Add(new Pair<Keys, string>(Keys.Shift, "Shift"));
            AllowedModifiers.Add(new Pair<Keys, string>(Keys.LWin, "Windows"));
            AllowedModifiers.Add(new Pair<Keys, string>(Keys.RWin, "Windows"));

            AllowedKeys.Add(new Pair<Keys, string>(Keys.Space, "Space"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Prior, "Prior"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Next, "Next"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.End, "End"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Home, "Home"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Left, "Left"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Up, "Up"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Right, "Right"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Down, "Down"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Insert, "Insert"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Delete, "Delete"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.D0, "0"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.D1, "1"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.D2, "2"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.D3, "3"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.D4, "4"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.D5, "5"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.D6, "6"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.D7, "7"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.D8, "8"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.D9, "9"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.A, "A"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.B, "B"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.C, "C"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.D, "D"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.E, "E"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F, "F"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.G, "G"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.H, "H"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.I, "I"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.J, "J"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.K, "K"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.L, "L"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.M, "M"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.N, "N"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.O, "O"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.P, "P"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Q, "Q"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.R, "R"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.S, "S"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.T, "T"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.U, "U"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.V, "V"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.W, "W"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.X, "X"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Y, "Y"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Z, "Z"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.NumPad0, "Numeric 0"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.NumPad1, "Numeric 1"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.NumPad2, "Numeric 2"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.NumPad3, "Numeric 3"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.NumPad4, "Numeric 4"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.NumPad5, "Numeric 5"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.NumPad6, "Numeric 6"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.NumPad7, "Numeric 7"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.NumPad8, "Numeric 8"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.NumPad9, "Numeric 9"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Multiply, "Multiply"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Add, "Add"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Separator, "Separator"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Subtract, "Substract"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Decimal, "Decimal"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Divide, "Divide"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F1, "F1"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F2, "F2"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F3, "F3"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F4, "F4"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F5, "F5"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F6, "F6"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F7, "F7"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F8, "F8"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F9, "F9"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F10, "F10"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F11, "F11"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F12, "F12"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F13, "F13"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F14, "F14"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F15, "F15"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F16, "F16"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F17, "F17"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F18, "F18"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F19, "F19"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F20, "F20"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F21, "F21"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F22, "F22"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F23, "F23"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.F24, "F24"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.BrowserBack, "Browser Back"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.BrowserForward, "Browser Forward"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.BrowserRefresh, "Browser Refresh"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.BrowserStop, "Browser Stop"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.BrowserSearch, "Browser Search"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.BrowserFavorites, "Browser Favorites"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.BrowserHome, "Browser Home"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.VolumeMute, "Mute"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.VolumeDown, "Volume Down"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.VolumeUp, "Volume Up"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.MediaNextTrack, "Media Next"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.MediaPreviousTrack, "Media Previous"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.MediaStop, "Media Stop"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.MediaPlayPause, "Media Play/Pause"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.LaunchMail, "Mail"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.SelectMedia, "Select Media"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.OemSemicolon, "OEM Semicolon"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Oemplus, "OEM Plus"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Oemcomma, "OEM Comma"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.OemMinus, "OEM Minus"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.OemPeriod, "OEM Period"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.OemQuestion, "OEM Question Mark"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Oemtilde, "OEM Tilde"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.OemOpenBrackets, "OEM Open Brackets"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.OemPipe, "OEM Pipe"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.OemCloseBrackets, "OEM Close Brackets"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.OemQuotes, "OEM Quotes"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.OemBackslash, "OEM Backslash"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Play, "Play"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Zoom, "Zoom"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Back, "Backspace"));
            AllowedKeys.Add(new Pair<Keys, string>(Keys.Delete, "Delete"));
        }

        private void UpdateText()
        {
            Text = Translate(_modifiers, _key);

            SelectionStart = Text.Length;
            SelectionLength = 0;
        }

        // TODO: extract this from control
        public static string Translate(Keys modifiers, Keys key)
        {
            StringBuilder builder = new StringBuilder();

            foreach (Pair<Keys, string> pair in AllowedModifiers)
            {
                if ((modifiers & pair.Key) == pair.Key)
                {
                    if (builder.Length > 0) 
                    {
                        builder.Append(", ");
                    }
                    builder.Append(pair.Value);
                }
            }

            if (builder.Length > 0)
            {
                builder.Append(" + ");
            }
            builder.Append(GetKeyCaption(key));

            return builder.ToString();
        }

        public void Set(Keys modifiers, Keys key)
        {
            _modifiers = modifiers;
            _key = key;
            _modified = false;

            UpdateText();
        }

        public bool Get(out Keys modifiers, out Keys key)
        {
            modifiers = _modifiers;
            key = _key;

            return modifiers != Keys.None && key != Keys.None;
        }

        public new bool Modified
        {
            get
            {
                return _modified;
            }
            set
            {
                _modified = value;
            }
        }

        protected override bool InternalIsValid()
        {
            return true;
        }
    }
}
